{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "b637bed2e4b49784",
   "metadata": {
    "id": "b637bed2e4b49784"
   },
   "outputs": [],
   "source": [
    "# Inline Plotting\n",
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "87a8f4955e1268aa",
   "metadata": {
    "id": "87a8f4955e1268aa"
   },
   "source": [
    "# Multi-Agent Reinforcement Learning Experiment\n",
    "\n",
    "Note: This is an implement of the MADDPG algorithm based on the essay \"Multi-Agent Actor-Critic for Mixed Cooperative-Competitive Environments\"."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a835a163e1633f4f",
   "metadata": {
    "id": "a835a163e1633f4f"
   },
   "source": [
    "## 0. Preparation"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "73e48db0",
   "metadata": {},
   "source": [
    "At the very beginning, let's import needed packages. We choose pettingzoo as our environment, installed by using `pip`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1ef0bfb943d38916",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "1ef0bfb943d38916",
    "outputId": "af96fd11-148e-42d4-c5e3-913634c598dd"
   },
   "outputs": [],
   "source": [
    "# Install packages\n",
    "%pip install pettingzoo[mpe]\n",
    "%pip install openpyxl"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eaddc110",
   "metadata": {},
   "source": [
    "We'll also use the following from PyTorch:\n",
    "\n",
    "- neural networks (torch.nn)\n",
    "- optimization (torch.optim)\n",
    "\n",
    "Matplotlib and Openpyxl are used for saving and visualizing our results."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "c85afac75c84429b",
   "metadata": {
    "id": "c85afac75c84429b"
   },
   "outputs": [],
   "source": [
    "# Import libraries\n",
    "import random\n",
    "from collections import namedtuple, deque\n",
    "\n",
    "import matplotlib\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "import torch.optim as optim\n",
    "from matplotlib import pyplot as plt\n",
    "from pettingzoo.mpe import simple_adversary_v3\n",
    "from psutil import virtual_memory"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e6ffeaa2",
   "metadata": {},
   "source": [
    " Now,let's check the system memory and  NVIDIA Information."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "8521bc707b30e9f",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "8521bc707b30e9f",
    "outputId": "b5c522f4-b318-4f99-c02a-aef8441595b2"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Thu Aug 17 17:15:55 2023       \n",
      "+---------------------------------------------------------------------------------------+\n",
      "| NVIDIA-SMI 536.99                 Driver Version: 536.99       CUDA Version: 12.2     |\n",
      "|-----------------------------------------+----------------------+----------------------+\n",
      "| GPU  Name                     TCC/WDDM  | Bus-Id        Disp.A | Volatile Uncorr. ECC |\n",
      "| Fan  Temp   Perf          Pwr:Usage/Cap |         Memory-Usage | GPU-Util  Compute M. |\n",
      "|                                         |                      |               MIG M. |\n",
      "|=========================================+======================+======================|\n",
      "|   0  NVIDIA GeForce RTX 4060 ...  WDDM  | 00000000:01:00.0 Off |                  N/A |\n",
      "| N/A   35C    P0              11W / 120W |      0MiB /  8188MiB |      0%      Default |\n",
      "|                                         |                      |                  N/A |\n",
      "+-----------------------------------------+----------------------+----------------------+\n",
      "                                                                                         \n",
      "+---------------------------------------------------------------------------------------+\n",
      "| Processes:                                                                            |\n",
      "|  GPU   GI   CI        PID   Type   Process name                            GPU Memory |\n",
      "|        ID   ID                                                             Usage      |\n",
      "|=======================================================================================|\n",
      "|  No running processes found                                                           |\n",
      "+---------------------------------------------------------------------------------------+\n",
      "Your runtime has 16.9 gigabytes of available RAM\n",
      "\n",
      "Not using a high-RAM runtime\n"
     ]
    }
   ],
   "source": [
    "# Run in the Colab\n",
    "run_in_colab = False\n",
    "if run_in_colab:\n",
    "    # Check NVIDIA information\n",
    "    gpu_info = !nvidia-smi\n",
    "    gpu_info = '\\n'.join(gpu_info)\n",
    "    if gpu_info.find('failed') >= 0:\n",
    "        print('Not connected to a GPU')\n",
    "    else:\n",
    "        print(gpu_info)\n",
    "    # Check memory information\n",
    "    ram_gb = virtual_memory().total / 1e9\n",
    "    print('Your runtime has {:.1f} gigabytes of available RAM\\n'.format(ram_gb))\n",
    "    if ram_gb < 20:\n",
    "        print('Not using a high-RAM runtime')\n",
    "    else:\n",
    "        print('You are using a high-RAM runtime!')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "fb0b801653d795ca",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "fb0b801653d795ca",
    "outputId": "9644f8de-be9f-4726-d840-e69cc39e862b"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<contextlib.ExitStack at 0x1d3e3c9e590>"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Set up matplotlib\n",
    "is_ipython = 'inline' in matplotlib.get_backend()\n",
    "if is_ipython:\n",
    "    from IPython import display\n",
    "\n",
    "plt.ion()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "82447ad863fddf",
   "metadata": {
    "id": "82447ad863fddf"
   },
   "source": [
    "## 1. Replay Buffer"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4e424ef7",
   "metadata": {},
   "source": [
    "Replay Buffer is a method of data storage and retrieval used in Reinforcement Learning. Its principle is to store the agent's experience in the environment in a buffer, and then to randomly extract some of the data from the buffer during training for learning. This has two advantages: first, it improves the utilisation rate of the data and avoids the waste of discarding it only once each time; second, it breaks the temporal correlation of the data and reduces the instability of the training. We chose to implement it with a circular queue of fixed size, and when the queue is full, the oldest data is automatically discarded.\n",
    "\n",
    "For this, we're going to need two classes:\n",
    "\n",
    "- `Transition` - a named tuple representing a single transition in our environment. It essentially maps (state, action) pairs to their (next_state, reward) result, with the state being the screen difference image as described later on.\n",
    "- `ReplayBuffer` - a cyclic buffer of bounded size that holds the transitions observed recently. It also implements a `.sample()` method for selecting a random batch of transitions for training."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "dd698432ed269d7b",
   "metadata": {
    "id": "dd698432ed269d7b"
   },
   "outputs": [],
   "source": [
    "\"\"\"Transition is a namedtuple used to store a transition.\n",
    "\n",
    "The structure of Transition looks like this:\n",
    "    (state, action, reward, next_state, done)\n",
    "\"\"\"\n",
    "Transition = namedtuple('Transition', ('state', 'action', 'reward', 'next_state', 'done'))\n",
    "\n",
    "\n",
    "class ReplayBuffer:\n",
    "    \"\"\"ReplayBuffer is a class used to achieve experience replay.\n",
    "\n",
    "    It's a buffer composed of a deque with a certain capacity. When the deque is full, it will automatically remove the oldest transition in the buffer.\n",
    "\n",
    "    Attributes:\n",
    "        _storage: The buffer to store the transitions.\n",
    "\n",
    "    Examples:\n",
    "        A replay buffer structure looks like below:\n",
    "        [\n",
    "            (state_1, action_1, reward_1, next_state_1, done_1),\n",
    "            (state_2, action_2, reward_2, next_state_2, done_2),\n",
    "            ...\n",
    "            (state_n, action_n, reward_n, next_state_n, done_n),\n",
    "        ]\n",
    "        Each tuple is a transition.\n",
    "    \"\"\"\n",
    "\n",
    "    def __init__(self, capacity=100_000):\n",
    "        \"\"\"Initial a replay buffer with capacity.\n",
    "\n",
    "        Args:\n",
    "            capacity: Max length of the buffer.\n",
    "        \"\"\"\n",
    "        self._storage = deque([], maxlen=capacity)\n",
    "\n",
    "    def add(self, state, action, reward, next_state, done):\n",
    "        \"\"\"Add a transition to the buffer.\n",
    "\n",
    "        Args:\n",
    "            state:          The state of the agents.\n",
    "            action:         The action of the agents.\n",
    "            reward:         The reward of the agents.\n",
    "            next_state:     The next state of the agents.\n",
    "            done:           The termination of the agents.\n",
    "\n",
    "        Returns: None\n",
    "\n",
    "        \"\"\"\n",
    "        transition = Transition(state, action, reward, next_state, done)\n",
    "        self._storage.append(transition)\n",
    "\n",
    "    def sample(self, batch_size):\n",
    "        \"\"\"Sample a batch of transitions from the buffer.\n",
    "\n",
    "        Args:\n",
    "            batch_size: The number of transitions that we want to sample from the buffer.\n",
    "\n",
    "        Returns: A batch of transitions.\n",
    "\n",
    "        Example:\n",
    "            Assuming that batch_size=3, we'll randomly sample 3 transitions from the buffer:\n",
    "                state_batch = (state_1, state_2, state_3)\n",
    "                action_batch = (action_1, action_2, action_3)\n",
    "                reward_batch = (reward_1, reward_2, reward_3)\n",
    "                next_state_batch = (next_state_1, next_state_2, next_state_3)\n",
    "                done_batch = (done_1, done_2, done_3)\n",
    "        \"\"\"\n",
    "        transitions = random.sample(self._storage, batch_size)\n",
    "        state_batch, action_batch, reward_batch, next_state_batch, done_batch = zip(*transitions)\n",
    "        return state_batch, action_batch, reward_batch, next_state_batch, done_batch\n",
    "\n",
    "    def __len__(self):\n",
    "        \"\"\"Return the length of the buffer.\"\"\"\n",
    "        return len(self._storage)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1e015a5f56d7876f",
   "metadata": {
    "id": "1e015a5f56d7876f"
   },
   "source": [
    "## 2. Simple Neural Network"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ed49e00c",
   "metadata": {},
   "source": [
    "This code is a simple neural network class written in the PyTorch framework. It has the following characteristics:\n",
    "\n",
    "- It inherits the `nn.Module` class, which is the base class for all neural network models in PyTorch.\n",
    "- It defines three fully connected layers (`nn.Linear`), which are the input layer, the hidden layer and the output layer. Its parameters are input dimensions, output dimensions and hidden dimensions.\n",
    "- It defines the `forward` propagation process of the neural network in the forward method. It first passes the input data $x$ through the first layer and applies the ReLU activation function (`F.relu`) to it. It then passes the result through the second layer again and applies the `relu` activation function again. Finally, it passes the result through the third layer and returns the output data.\n",
    "\n",
    "The reasons we use three-layer neural networks are as follows:\n",
    "- Each linear layer introduces additional parameters and nonlinear activation functions, which increase the complexity and flexibility of the model. More linear layers mean that models can learn richer feature representations and better fit complex relationships between inputs and outputs\n",
    "- If the model is too complex and the amount of training data is relatively small, it is prone to overfitting problems. Too many linear layers increase the complexity of the model and make it easier to remember the details of the training sample, ignoring the ability to generalize to new data.\n",
    "- DQN is based on Q-learning algorithm ，which usually adopts three-layer CNN structure."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "a5729db93ad71867",
   "metadata": {
    "id": "a5729db93ad71867"
   },
   "outputs": [],
   "source": [
    "class SimpleNet(nn.Module):\n",
    "    \"\"\"SimpleNet is a 3-layer simple neural network.\n",
    "\n",
    "    It's used to approximate the policy functions and the value functions.\n",
    "    \"\"\"\n",
    "\n",
    "    def __init__(self, input_dim, output_dim, hidden_dim):\n",
    "        \"\"\"Initial a simple network for an actor or a critic.\n",
    "\n",
    "        Args:\n",
    "            input_dim: The input dimension of the network.\n",
    "            output_dim: The output dimension of the network.\n",
    "            hidden_dim: The hidden layer dimension of the network.\n",
    "        \"\"\"\n",
    "        super(SimpleNet, self).__init__()\n",
    "        self.layer1 = nn.Linear(input_dim, hidden_dim)\n",
    "        self.layer2 = nn.Linear(hidden_dim, hidden_dim)\n",
    "        self.layer3 = nn.Linear(hidden_dim, output_dim)\n",
    "\n",
    "    def forward(self, x):\n",
    "        x = F.relu(self.layer1(x))\n",
    "        x = F.relu(self.layer2(x))\n",
    "        return self.layer3(x)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cae7c08737dd548f",
   "metadata": {
    "id": "cae7c08737dd548f"
   },
   "source": [
    "## 3. Gumbel Softmax Trick"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2c0b6c7b",
   "metadata": {},
   "source": [
    "The action space of each agent in an MPE environment is discrete. The DDPG algorithm we use must itself make the agent's actions derivable for its strategy parameters, which is true for continuous action space, but not for discrete action space. However, this does not mean that the current task cannot be solved using the MADDPG algorithm, because we can use a method called Gumbel-Softmax to get an approximate sample of the discrete distribution. Below we briefly introduce its principles and provide the implementation code.\n",
    "\n",
    "Suppose you have a random variable that follows a discrete distribution $K=(a_1,...,a_k)$. Then $a_i\\in[0,1]$ means $P(Z=i)$ and $\\sum_{i = 1}^{k}a_i=1$. If we want to sample according to this distribution $z\\sim K$, we can see that sampling this discrete distribution is not derivable.\n",
    "\n",
    "So is there a way to make discrete sampling controllable? The answer is heavy parameterisation, and we use the Gumbel-Softmax technique. Specifically, we introduce a heavy parameter factor $g_i$, which is a noise sampled from $Gumbel(0，1)$: $$g_i=-log(-logu),u\\sim Uniform(0,1)$$Gumbel Softmax samples can be written as:$$y_i=\\frac{exp((loga_i+g_i)/\\tau)}{\\sum_{j = 1}^{k}exp((loga_j+g_i)/\\tau)},i=1,...,k.$$\n",
    "\n",
    "In this case, if the discrete value is calculated by $z=\\arg max_iy_i$, the discrete value is approximately equivalent to the value sampled by the discrete $z\\sim K$. Furthermore, the gradient for $a$ is naturally introduced into the sample result $y$. $\\tau>0$ is called the temperature parameter of the distribution, and by adjusting it one can control how similar the generated Gumbel-Softmax distribution is to the discrete distribution: the smaller $\\tau$, the more the resulting distribution tends to be the result $onehot(\\arg max_iloga_i+g_i)$; the larger $\\tau$, the more the resulting distribution tends to be uniformly distributed.\n",
    "\n",
    "Next, define some of the utility functions that need to be used, including functions related to Gumbel Softmax sampling, which allows DDPG to be applied to discrete action spaces."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "337f3cc479b8d87b",
   "metadata": {
    "id": "337f3cc479b8d87b"
   },
   "outputs": [],
   "source": [
    "def trans2onehot(logits, eps=0.01):\n",
    "    \"\"\"Transform the output of the actor network to a one-hot vector.\n",
    "\n",
    "    Args:\n",
    "        logits:     The output value of the actor network.\n",
    "        eps:        The epsilon parameter in the epsilon-greedy algorithm used to choose an action.\n",
    "\n",
    "    Returns: An action in one-hot vector form.\n",
    "\n",
    "    \"\"\"\n",
    "    # Generates a one-hot vector form of the action selected by the actor network.\n",
    "    best_action = (logits == logits.max(1, keepdim=True)[0]).float()\n",
    "    # Generate a one-hot vector form of a random action.\n",
    "    size, num_actions = logits.shape\n",
    "    random_index = np.random.choice(range(num_actions), size=size)\n",
    "    random_actions = torch.eye(num_actions)[[random_index]].to(logits.device)\n",
    "    # Select an action using the epsilon-greedy algorithm.\n",
    "    random_mask = torch.rand(size, device=logits.device) <= eps\n",
    "    selected_action = torch.where(random_mask.view(-1, 1), random_actions, best_action)\n",
    "    return selected_action\n",
    "\n",
    "\n",
    "def sample_gumbel(shape, eps=1e-20, tensor_type=torch.float32):\n",
    "    \"\"\"Sample a Gumbel noise from the Gumbel(0,1) distribution.\"\"\"\n",
    "    U = torch.rand(shape, dtype=tensor_type, requires_grad=False)\n",
    "    gumbel_noise = -torch.log(-torch.log(U + eps) + eps)\n",
    "    return gumbel_noise\n",
    "\n",
    "\n",
    "def gumbel_softmax_sample(logits, temperature):\n",
    "    \"\"\"Sample from the Gumbel-Softmax distribution.\"\"\"\n",
    "    gumbel_noise = sample_gumbel(logits.shape, tensor_type=logits.dtype).to(logits.device)\n",
    "    y = logits + gumbel_noise\n",
    "    return F.softmax(y / temperature, dim=1)\n",
    "\n",
    "\n",
    "def gumbel_softmax(logits, temperature=1.0):\n",
    "    \"\"\"Sample from Gumbel-Softmax distribution and discretize it.\n",
    "\n",
    "    By returning a one-hot vector of y_hard, but with a gradient of y, we can get both a discrete action interacting with the environment and a correct inverse gradient.\n",
    "    \"\"\"\n",
    "    y = gumbel_softmax_sample(logits, temperature)\n",
    "    y_hard = trans2onehot(y)\n",
    "    y = (y_hard.to(logits.device) - y).detach() + y\n",
    "    return y"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9432ad7b1133c07d",
   "metadata": {
    "id": "9432ad7b1133c07d"
   },
   "source": [
    "## 4. DDPG"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9030e0f3",
   "metadata": {},
   "source": [
    "Next we will use the deep deterministic policy gradient (DDPG) algorithm, which constructs a deterministic policy that uses gradient ascent to maximize values. DDPG is also an actor-critic algorithm. \n",
    "\n",
    "If the policy is deterministic, it can be denoted by $a=u_\\theta(s)$. Similar to the policy gradient theorem, we can derive the deterministic policy gradient theorem:$$\\nabla_{\\theta} J\\left(\\pi_{\\theta}\\right)=\\mathbb{E}_{s \\sim \\nu^{\\pi_{\\beta}}}\\left[\\left.\\nabla_{\\theta} \\mu_{\\theta}(s) \\nabla_{a} Q_{\\omega}^{\\mu}(s, a)\\right|_{a=\\mu_{\\theta}(s)}\\right]$$\n",
    "\n",
    "where $\\pi _\\beta$ is the policy used to collect data. We can understand this theorem as follows: suppose there is already a function $Q$ given a state $s$. But since the action space is now infinite, it is not possible to find the action with the largest value of $Q$ by iterating over all actions, so we want to use the policy $u$ to find the action $a$ that maximises the value of $Q(s,a)$: $u(s)=\\arg max_aQ(s,a)$.\n",
    "\n",
    "To get $u$, we first use $Q$ to derive a derivative $\\nabla _\\theta Q(s,u_\\theta (s))$ of $u_\\theta$, which uses the chain rule of the gradient, first for $a$ and then for $\\theta$. The function $Q$ is then maximised by a gradient ascent to get the action with the largest $Q$ value.\n",
    "\n",
    "DDPG uses four neural networks, one each for Actor and Critic, and one each for a target network. In DDPG, Actors also need a target net, as the target net is also used to calculate the target value $Q$. The update of the target &Q& network in DDPG is slightly different from that in DQN: in DQN, the $Q$ network is replicated directly to the target $Q$ network at regular intervals; in DDPG, the update of the target $Q$ network uses a soft update method, i.e. the target $Q$ network is updated slowly and gradually approaches the $Q$ network, and its formula is$$w^-\\leftarrow \\tau w+(1-\\tau)w^-$$\n",
    "\n",
    "Usually $\\tau$ is a relatively small number, and when $\\tau=1$ it is consistent with how the DQN is updated. The target network $u$ also uses this soft update method.\n",
    "\n",
    "In addition, due to the problem of overestimating the $Q$ value of the function $Q$, DDPG uses the technology in Double DQN to update the network. However, because DDPG uses a deterministic strategy, its own exploration is still very limited. Remembering the DQN algorithm, its exploration is mainly generated by the behaviour of the greedy strategy. Also as an algorithm for offline strategies, DDPG introduces a random noise on the behavioural strategies to explore.\n",
    "\n",
    "Both the strategy network and the value network use a single hidden layer neural network. The output layer of the strategy network uses the tangent function $y=\\tanh x$ as the activation function, because the value domain of the tangent function is $[-1,1]$, which is convenient to scale to the range of actions acceptable to the environment. In DDPG, which deals with environments that interact with continuous actions, the input to the x-network is a vector of states and actions concatenated together, and the output of the x-network is a value representing the value of that state-action pair.\n",
    "\n",
    "When taking actions with a policy network, we add Gaussian noise to the actions for better exploration. In the original DDPG paper, the added noise corresponds to the Ornstein-Uhlenbeck (OU) stochastic process:$$\\Delta x_{t}=\\theta\\left(\\mu-x_{t-1}\\right)+\\sigma W$$\n",
    "\n",
    "where $u$ is the mean, $W$ is random noise following Brownian motion, and $\\theta$ and $\\sigma$ are scale parameters. It can be seen that $x(t-1)$ when deviating from the mean, the value of $x_t$ moves closer to the mean. the OU stochastic process is characterised by a linear negative feedback around the mean with an additional disturbance term."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "72c554e4380ea6fb",
   "metadata": {
    "id": "72c554e4380ea6fb"
   },
   "outputs": [],
   "source": [
    "class DDPG:\n",
    "    \"\"\"The DDPG Algorithm.\n",
    "\n",
    "    1. Each instance of DDPG corresponds an agent.\n",
    "    2. Each instance of DDPG consists of an actor (policy network) and a critic (value network).\n",
    "    3. Each instance of DDPG contains a set of target networks for its actor and critic (affected by the double DQN strategy).\n",
    "    4. Network update function is contained in the Center Controller class, the MADDPG class, so that we can achieve the Centralized Training and Decentralized Execution method easily.\n",
    "\n",
    "    Attributes:\n",
    "        actor:              The Actor (Policy Network).\n",
    "        target_actor:       The Target Actor (Target Policy Network).\n",
    "        critic:             The Critic (value Network).\n",
    "        target_critic:      The Target Critic (Target Value Network).\n",
    "        actor_optimizer:    The optimizer of Actor.\n",
    "        critic_optimizer:   The optimizer of Critic.\n",
    "    \"\"\"\n",
    "\n",
    "    def __init__(self, state_dim, action_dim, critic_dim, hidden_dim, actor_lr, critic_lr, device):\n",
    "        \"\"\"Initialize a DDPG instance for an agent.\n",
    "\n",
    "        Args:\n",
    "            state_dim:      The dimension of the state, which is also the input dimension of Actor and a part of Critics' input.\n",
    "            action_dim:     The dimension of the action, which is also a part of Critics' input.\n",
    "            critic_dim:     The dimension of the Critics' input (Critic Dimension = State Dimensions + Action Dimension).\n",
    "            hidden_dim:     The dimension of the hidden layer of the networks.\n",
    "            actor_lr:       The learning rate for the Actor.\n",
    "            critic_lr:      The learning rate for the Critic.\n",
    "            device:         The device to compute.\n",
    "        \"\"\"\n",
    "        # Set Actor with Target Network\n",
    "        self.actor = SimpleNet(state_dim, action_dim, hidden_dim).to(device)\n",
    "        self.target_actor = SimpleNet(state_dim, action_dim, hidden_dim).to(device)\n",
    "        # Set Critic with Target Network\n",
    "        self.critic = SimpleNet(critic_dim, 1, hidden_dim).to(device)\n",
    "        self.target_critic = SimpleNet(critic_dim, 1, hidden_dim).to(device)\n",
    "        # Load parameters from Actor and Critic to their target networks\n",
    "        self.target_actor.load_state_dict(self.actor.state_dict())\n",
    "        self.target_critic.load_state_dict(self.critic.state_dict())\n",
    "        # Set up optimizers\n",
    "        self.actor_optimizer = optim.Adam(self.actor.parameters(), lr=actor_lr)\n",
    "        self.critic_optimizer = optim.Adam(self.critic.parameters(), lr=critic_lr)\n",
    "\n",
    "    def take_action(self, state, explore=False):\n",
    "        \"\"\"Take action from the Actor (Policy Network).\n",
    "\n",
    "        1. State -> the Actor -> Action Value\n",
    "        2. Choose action according to the value of explore.\n",
    "            - explore == True: Choose an action based on the gumbel trick.\n",
    "            - explore == False: Choose the action from the Actor, and transform the value to a one-hot vector.\n",
    "\n",
    "        Args:\n",
    "            state:      The partial observation of the agent.\n",
    "            explore:    The strategy to choose action.\n",
    "\n",
    "        Returns: The action that the Actor has chosen.\n",
    "\n",
    "        \"\"\"\n",
    "        # Choose an action from actor network (deterministic policy network).\n",
    "        action = self.actor(state)\n",
    "        # Exploration and Exploitation\n",
    "        if explore:\n",
    "            action = gumbel_softmax(action)\n",
    "        else:\n",
    "            action = trans2onehot(action)\n",
    "        # TODO: Find out why the action need to be transferred to the CPU\n",
    "        # return action.detach().cpu().numpy()[0]\n",
    "        return action.detach().cpu().numpy()[0]\n",
    "\n",
    "    @staticmethod\n",
    "    def soft_update(net, target_net, tau):\n",
    "        \"\"\"Soft update function, which is used to update the parameters in the target network.\n",
    "\n",
    "        Args:\n",
    "            net:            The original network.\n",
    "            target_net:     The target network.\n",
    "            tau:            Soft update parameter.\n",
    "\n",
    "        Returns: None\n",
    "\n",
    "        \"\"\"\n",
    "        # Update target network's parameters using soft update strategy\n",
    "        for target_param, param in zip(target_net.parameters(), net.parameters()):\n",
    "            target_param.data.copy_(tau * param.data + (1.0 - tau) * target_param.data)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "26f6bd6a4a3305e3",
   "metadata": {
    "id": "26f6bd6a4a3305e3"
   },
   "source": [
    "## 5. MADDPG"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "83228c56",
   "metadata": {},
   "source": [
    "The multi-agent DDPG algorithm literally means an algorithm that implements a DDPG for each agent. All agents share a centralised critic network that simultaneously guides each agent's actor network during the training process, and each agent's actor network acts completely independently, i.e. decentralised execution.\n",
    "The main detail of the MADDPG algorithm is that each agent is trained with the Actor-Critic method, but unlike the traditional single agent, the Critic part of each agent in MADDPG can obtain the strategy information of other agents. Specifically, if we consider a game with $N$ agents, each agent's policy parameter is $\\theta=\\lbrace \\theta_1,...,\\theta _N \\rbrace$, and $\\pi=\\lbrace \\pi_1,...,\\pi _N \\rbrace$ is the set of strategies for all agents, then we can write the policy gradient of each agent's expected return in the case of a random strategy:$$\\nabla_{\\theta_{i}} J\\left(\\theta_{i}\\right)=\\mathbb{E}_{s \\sim p^{\\mu}, a \\sim \\pi_{i}}\\left[\\nabla_{\\theta_{i}} \\log \\pi_{i}\\left(a_{i} \\mid o_{i}\\right) Q_{i}^{\\pi}\\left(\\mathbf{x}, a_{1}, \\ldots, a_{N}\\right)\\right]$$\n",
    "\n",
    "$Q_i^\\pi=(\\mathbf{x},a_1,...,a_N)$ is a centralised action value function. Why is $Q_i$ a centralised action value function? In general, $\\mathbf{x}=(o_1,...,o_N)$ contains the observations of all agents, and $Q_i$ must also contain the actions of all agents at that moment, so the premise of $Q_i$ is that all agents must give their own observations and corresponding actions at the same time.\n",
    "\n",
    "For a deterministic strategy, considering that there are now $N$ successive strategies $\\mu_{\\theta_{i}}$, we can get the gradient formula for DDPG:$$\\nabla_{\\theta_{i}} J\\left(\\theta_{i}\\right)=\\mathbb{E}_{s \\sim p^{\\mu}, a \\sim \\pi_{i}}\\left[\\nabla_{\\theta_{i}} \\log \\pi_{i}\\left(a_{i} \\mid o_{i}\\right) Q_{i}^{\\pi}\\left(\\mathbf{x}, a_{1}, \\ldots, a_{N}\\right)\\right]\\nabla_{\\theta_{i}} J\\left(\\mu_{i}\\right)=\\mathbb{E}_{\\mathbf{x} \\sim \\mathcal{D}}\\left[\\left.\\nabla_{\\theta_{i}} \\mu_{i}\\left(o_{i}\\right) \\nabla_{a_{i}} Q_{i}^{\\mu}\\left(\\mathbf{x}, a_{1}, \\ldots, a_{N}\\right)\\right|_{a_{i}=\\mu_{i}\\left(o_{i}\\right)}\\right]$$\n",
    "\n",
    "$D$ is the empirical playback pool we use to store data, and every piece of data it stores is $(\\mathbf{x},\\mathbf{x}',a_1,...,a_N,r_1,...,r_N)$. In MADDPG, the centralized action value function can be updated with the following loss function:$$\\mathcal{L}\\left(\\omega_{i}\\right)=\\mathbb{E}_{\\mathbf{x}, a, r, \\mathbf{x}^{\\prime}}\\left[\\left(Q_{i}^{\\mu}\\left(\\mathbf{x}, a_{1}, \\ldots, a_{N}\\right)-y\\right)^{2}\\right], \\quad y=r_{i}+\\left.\\gamma Q_{i}^{\\mu^{\\prime}}\\left(\\mathbf{x}^{\\prime}, a_{1}^{\\prime}, \\ldots, a_{N}^{\\prime}\\right)\\right|_{a_{j}^{\\prime}=\\mu_{j}^{\\prime}\\left(o_{j}\\right)}$$\n",
    "\n",
    "$\\mu^{\\prime}=\\left(\\mu_{\\theta_{1}}^{\\prime}, \\ldots, \\mu_{\\theta_{N}}^{\\prime}\\right)$ is the set of target policies used in the update value function, which have parameters for deferred updates.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "4ff8104372bbaafe",
   "metadata": {
    "id": "4ff8104372bbaafe"
   },
   "outputs": [],
   "source": [
    "class MADDPG:\n",
    "    \"\"\"The Multi-Agent DDPG Algorithm.\n",
    "\n",
    "    1. The instance of MADDPG is the Center Controller in the algorithm.\n",
    "    2. The instance of MADDPG contains a list of DDPG instances, which are corresponded with the agents in the environment one by one.\n",
    "\n",
    "    Attributes:\n",
    "        agents:     A list of DDPG instances, which are corresponded with the agents in the environment one by one.\n",
    "        device:     The device to compute.\n",
    "        gamma:      The gamma parameter in TD target.\n",
    "        tau:        The tau parameter for soft update.\n",
    "        critic_criterion: The loss function for the Critic networks.\n",
    "    \"\"\"\n",
    "\n",
    "    def __init__(self, state_dims, action_dims, critic_dim, hidden_dim, actor_lr, critic_lr, device, gamma, tau):\n",
    "        \"\"\"Initialize a MADDPG instance as the Center Controller.\n",
    "\n",
    "        Args:\n",
    "            state_dims: A list of dimensions of each agent's observation.\n",
    "            action_dims: A list of dimensions of each agent's action.\n",
    "            critic_dim: The dimension of the Critic networks' input.\n",
    "            hidden_dim: The dimension of the networks' hidden layers.\n",
    "            actor_lr: The learning rate for the Actor.\n",
    "            critic_lr: The learning rate for the Critic.\n",
    "            device: The device to compute.\n",
    "            gamma: The gamma parameter in TD target.\n",
    "            tau: The tau parameter for soft update.\n",
    "        \"\"\"\n",
    "        # TODO: Should we use dict to combine the DDPG instance with agents?\n",
    "        self.agents = [\n",
    "            DDPG(state_dim, action_dim, critic_dim, hidden_dim, actor_lr, critic_lr, device)\n",
    "            for state_dim, action_dim in zip(state_dims, action_dims)\n",
    "        ]\n",
    "        self.device = device\n",
    "        self.gamma = gamma\n",
    "        self.tau = tau\n",
    "        self.critic_criterion = nn.MSELoss()\n",
    "\n",
    "    @property\n",
    "    def policies(self):\n",
    "        \"\"\"A list of Actors for the agents.\"\"\"\n",
    "        return [agent.actor for agent in self.agents]\n",
    "\n",
    "    @property\n",
    "    def target_policies(self):\n",
    "        \"\"\"A list of Target Actors for the agents.\"\"\"\n",
    "        return [agent.target_actor for agent in self.agents]\n",
    "\n",
    "    def take_action(self, states, explore):\n",
    "        \"\"\"Take actions from the Actors (Policy Networks).\n",
    "\n",
    "        Args:\n",
    "            states: A list of observations from all the agents.\n",
    "            explore: The strategy to choose action (Exploration or Exploitation).\n",
    "\n",
    "        Returns: A list of actions in one-hot vector form.\n",
    "\n",
    "        \"\"\"\n",
    "        states = [torch.tensor(np.array([state]), dtype=torch.float, device=self.device) for state in states]\n",
    "        return [agent.take_action(state, explore) for agent, state in zip(self.agents, states)]\n",
    "\n",
    "    def update(self, sample, agent_idx):\n",
    "        \"\"\"Update parameters for the agent whose index is agent_idx.\n",
    "\n",
    "        Args:\n",
    "            sample: A batch of transitions used to update all the parameters.\n",
    "            agent_idx: The index of the agent whose Actor and Critic would be updated in this function.\n",
    "\n",
    "        Returns: None\n",
    "\n",
    "        Process:\n",
    "            Parse Data from Sample to Observation, Action, Reward, Next Observation and Done Tag.\n",
    "            Set up Current Agent\n",
    "            Update Current Critic (Value Network) with TD Algorithm:\n",
    "                1. Initialize the Gradient to Zero.\n",
    "                2. Build a Tensor Contains All Target Actions Using Target Actor Networks and Next Observations.\n",
    "                3. Calculate Target Critic Value:\n",
    "                    - Combine Next Observation and Target Action in One To One Correspondence.\n",
    "                    - Calculate Target Critic Value (TD Target).\n",
    "                4. Calculate Critic Value:\n",
    "                    - Combine Observation and Action in One To One Correspondence.\n",
    "                    - Calculate Critic Value (TD Target).\n",
    "                5. Calculate Critic's Loss Using MSELoss Function\n",
    "                6. Backward Propagation.\n",
    "                7. Update Parameters with Gradient Descent.\n",
    "            Update Current Actor (Policy Network) with Deterministic Policy Gradient:\n",
    "                1. Initialize the Gradient to Zero.\n",
    "                2. Get Current Actors' Action in the Shape of One-Hot Vector.\n",
    "                    - Get Current Actor Network Output with Current Observation.\n",
    "                    - Transform the Output into a One-Hot Action Vector.\n",
    "                3. Build the Input of Current Actor's Value Function.\n",
    "                    - Build a tensor that contains all the actions.\n",
    "                    - Combine Observations and Actions in One To One Correspondence.\n",
    "                4. Calculate Actor's Loss Using Critic Network (Value Function) Output.\n",
    "                5. Backward Propagation.\n",
    "                6. Update Parameters with Gradient Descent.\n",
    "\n",
    "        TODO:\n",
    "        1. Why there is a term, \"(1 - dones[agent_idx].view(-1, 1))\", during calculation of TD target?\n",
    "            To take termination in to consideration (To be verified).\n",
    "        2. Why there is a term, \"(current_actor_action_value ** 2).mean() * 1e-3\", during calculation of Actor Loss?\n",
    "            To make the output of the trained Actor more stable and smooth with this regular term (To be verified).\n",
    "        \"\"\"\n",
    "        states, actions, rewards, next_states, dones = sample\n",
    "        current_agent = self.agents[agent_idx]\n",
    "        # Update Current Critic (Value Network) with TD Algorithm\n",
    "        current_agent.critic_optimizer.zero_grad()\n",
    "        # Here is the step to choose the actions from the actor network and we have two strategies.\n",
    "        # Option 1: Use the target network strategy.\n",
    "        target_action = [\n",
    "            trans2onehot(_target_policy(_next_obs))\n",
    "            for _target_policy, _next_obs in zip(self.target_policies, next_states)\n",
    "        ]\n",
    "        # Option 2: Use the double DQN strategy.\n",
    "        # target_action = [\n",
    "        #     # Choose actions from the original network!!!\n",
    "        #     trans2onehot(policy(_next_obs))\n",
    "        #     for policy, _next_obs in zip(self.policies, next_states)\n",
    "        # ]\n",
    "        target_critic_input = torch.cat((*next_states, *target_action), dim=1)\n",
    "        target_critic_value = (rewards[agent_idx].view(-1, 1) +\n",
    "                               self.gamma * current_agent.target_critic(target_critic_input) *\n",
    "                               (1 - dones[agent_idx].view(-1, 1)))\n",
    "        critic_input = torch.cat((*states, *actions), dim=1)\n",
    "        critic_value = current_agent.critic(critic_input)\n",
    "        critic_loss = self.critic_criterion(critic_value, target_critic_value.detach())\n",
    "        critic_loss.backward()\n",
    "        current_agent.critic_optimizer.step()\n",
    "        # Update Current Actor (Policy Network) with Deep Deterministic Policy Gradient\n",
    "        current_agent.actor_optimizer.zero_grad()\n",
    "        current_actor_action_value = current_agent.actor(states[agent_idx])\n",
    "        current_actor_action_onehot = gumbel_softmax(current_actor_action_value)\n",
    "        all_actor_actions = [\n",
    "            current_actor_action_onehot if i == agent_idx else trans2onehot(_policy(_state))\n",
    "            for i, (_policy, _state) in enumerate(zip(self.policies, states))\n",
    "        ]\n",
    "        current_critic_input = torch.cat((*states, *all_actor_actions), dim=1)\n",
    "        actor_loss = (-current_agent.critic(current_critic_input).mean() +\n",
    "                      (current_actor_action_value ** 2).mean() * 1e-3)\n",
    "        actor_loss.backward()\n",
    "        current_agent.actor_optimizer.step()\n",
    "\n",
    "    def update_all_targets_params(self):\n",
    "        \"\"\"Update all Target network's parameters using soft update method.\"\"\"\n",
    "        for agent in self.agents:\n",
    "            agent.soft_update(agent.actor, agent.target_actor, self.tau)\n",
    "            agent.soft_update(agent.critic, agent.target_critic, self.tau)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3ec7c54ff4830fb8",
   "metadata": {
    "id": "3ec7c54ff4830fb8"
   },
   "source": [
    "## 6. Evaluate Function"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "48052d9b",
   "metadata": {},
   "source": [
    "The function takes a MADDPG agent as input and evaluates its performance over a specified number of episodes and episode length."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "f14b583adb0e0113",
   "metadata": {
    "id": "f14b583adb0e0113"
   },
   "outputs": [],
   "source": [
    "def evaluate(num_agents, maddpg, num_episode=10, len_episode=25):\n",
    "    \"\"\"Evaluate the strategies for learning, and no exploration is undertaken at this time.\n",
    "\n",
    "    Args:\n",
    "        num_agents: The number of the agents.\n",
    "        maddpg: The Center Controller.\n",
    "        num_episode: The number of episodes.\n",
    "        len_episode: The length of each episode.\n",
    "\n",
    "    Returns: Returns list.\n",
    "\n",
    "    \"\"\"\n",
    "    env = simple_adversary_v3.parallel_env(N=num_agents, max_cycles=25, continuous_actions=False)\n",
    "    env.reset()\n",
    "    returns = np.zeros(env.max_num_agents)\n",
    "    # Create an array returns of zeros with a length equal to the number of agents in the environment.\n",
    "    # This array will store the cumulative returns for each agent.\n",
    "    for episode in range(num_episode):\n",
    "        states_dict, rewards_dict = env.reset()\n",
    "        states = [state for state in states_dict.values()]\n",
    "        for episode_step in range(len_episode):\n",
    "            actions = maddpg.take_action(states, explore=False)\n",
    "            # Take actions using the MADDPG agent (maddpg.take_action) based on the current states. \n",
    "            actions_SN = [np.argmax(onehot) for onehot in actions]\n",
    "            actions_dict = {env.agents[i]: actions_SN[i] for i in range(env.max_num_agents)}\n",
    "            next_states_dict, rewards_dict, terminations_dict, truncations_dict, _ = env.step(actions_dict)\n",
    "            # Take a step in the environment by passing the actions dictionary to env.step.  \n",
    "            rewards = [reward for reward in rewards_dict.values()]\n",
    "            next_states = [next_state for next_state in next_states_dict.values()]\n",
    "            states = next_states\n",
    "            rewards = np.array(rewards)\n",
    "            returns += rewards / num_episode\n",
    "    env.close()\n",
    "    return returns.tolist()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "25f288f40c5c2466",
   "metadata": {
    "id": "25f288f40c5c2466"
   },
   "source": [
    "## 7. Auxiliary Function"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eee6c454",
   "metadata": {},
   "source": [
    "The function rearranges the elements in the input list x and converts them into PyTorch tensors on the specified device."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "e3792d0073af4d80",
   "metadata": {
    "id": "e3792d0073af4d80"
   },
   "outputs": [],
   "source": [
    "def sample_rearrange(x, device):\n",
    "    \"\"\"Rearrange the transition in the sample.\"\"\"\n",
    "    rearranged = [[sub_x[i] for sub_x in x] for i in range(len(x[0]))]\n",
    "    return [torch.FloatTensor(np.vstack(attribute)).to(device) for attribute in rearranged]\n",
    "    # np.vstack is used to vertically stack the elements in each sublist before converting them to a tensor.\n",
    "    # The resulting tensors are then converted to the appropriate device (GPU)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1e279ba1b492ebc3",
   "metadata": {
    "id": "1e279ba1b492ebc3"
   },
   "source": [
    "## 8. Set up Parameters and Initialize Variables"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c9b4093d",
   "metadata": {},
   "source": [
    "It is time to initialize the necessary components and parameters for training an MADDPG agent in a multi-agent environment using the specified settings."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "dab6c59ea86f99f6",
   "metadata": {
    "id": "dab6c59ea86f99f6"
   },
   "outputs": [],
   "source": [
    "# Set up Parameters\n",
    "NUM_EPISODES = 5000\n",
    "LEN_EPISODES = 25  # The maximum length of each episode\n",
    "BUFFER_SIZE = 100_000\n",
    "HIDDEN_DIM = 64  # denotes the dimension of the hidden layers in the actor and critic networks.\n",
    "ACTOR_LR = 1e-2\n",
    "CRITIC_LR = 1e-2\n",
    "GAMMA = 0.95  # determines the discount factor for future rewards.\n",
    "TAU = 1e-2  # sets the soft update coefficient for target network updates.\n",
    "BATCH_SIZE = 1024\n",
    "UPDATE_INTERVAL = 100  # determines the number of steps before performing an update on the networks.\n",
    "MINIMAL_SIZE = 4000  # is a minimum threshold for the replay buffer size before starting the training.\n",
    "# Initialize Environment\n",
    "env = simple_adversary_v3.parallel_env(N=2, max_cycles=25, continuous_actions=False)\n",
    "env.reset()\n",
    "# Initialize Replay Buffer\n",
    "buffer = ReplayBuffer(BUFFER_SIZE)  # stores experiences from the environment.\n",
    "# Initialize Device\n",
    "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
    "# Initialize Return List\n",
    "return_list = []\n",
    "# Initialize Total Step\n",
    "total_step = 0\n",
    "# Initialize MADDPG\n",
    "# 1. Get State and Action Dimensions from the Environment\n",
    "state_dims = [env.observation_space(env.agents[i]).shape[0] for i in range(env.num_agents)]\n",
    "action_dims = [env.action_space(env.agents[i]).n for i in range(env.num_agents)]\n",
    "critic_dim = sum(state_dims) + sum(action_dims)  # calculates the total dimension of the critic network's input\n",
    "# 2. Create Center Controller\n",
    "maddpg = MADDPG(state_dims, action_dims, critic_dim, HIDDEN_DIM, ACTOR_LR, CRITIC_LR, device, GAMMA, TAU)\n",
    "# maddpg is created as an instance of the MADDPG class using the specified dimensions, learning rates and other parameters."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2da4352adbb6fed",
   "metadata": {
    "id": "2da4352adbb6fed"
   },
   "source": [
    "## 9. Train"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "76b73794",
   "metadata": {},
   "source": [
    "This code implements the training loop for the MADDPG agent, including taking actions, interacting with the environment, updating the networks, and periodically evaluating the agent's performance.\n",
    "- The take_action method is called on the MADDPG agent, with the argument explore=True. This means that the agent will add exploration noise to its action selection, allowing it to explore different actions instead of always selecting the same action deterministically.\n",
    "- The resulting actions are initially in a one-hot encoded format, representing the probability distribution over the possible actions for each agent.\n",
    "- By using numpy.argmax function, each one-hot encoded action is converted into a discrete action by selecting the index of the highest probability."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "edf02a977f035468",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "edf02a977f035468",
    "outputId": "c2471308-a137-4560-ca01-b114df0c6315"
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "C:\\Users\\16211\\AppData\\Local\\Temp\\ipykernel_19972\\802929597.py:59: UserWarning: Creating a tensor from a list of numpy.ndarrays is extremely slow. Please consider converting the list to a single numpy.ndarray with numpy.array() before converting to a tensor. (Triggered internally at ..\\torch\\csrc\\utils\\tensor_new.cpp:248.)\n",
      "  states = [torch.tensor([state], dtype=torch.float, device=self.device) for state in states]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Episode: 100, [-50.01294434092324, 27.477311759422665, 27.477311759422665]\n",
      "Episode: 200, [-50.211205566736965, 27.961839749566863, 27.961839749566863]\n",
      "Episode: 300, [-55.07178829785753, 28.13767456201035, 28.13767456201035]\n",
      "Episode: 400, [-27.454963220714607, 5.698475640452621, 5.698475640452621]\n",
      "Episode: 500, [-22.036688590797347, -3.6021900726724114, -3.6021900726724114]\n",
      "Episode: 600, [-21.28886724373529, -8.069581322846807, -8.069581322846807]\n",
      "Episode: 700, [-18.897598801324936, -0.6621882852209994, -0.6621882852209994]\n",
      "Episode: 800, [-19.646041894884437, 0.8053171735693928, 0.8053171735693928]\n",
      "Episode: 900, [-18.255108511452107, 2.472327855841341, 2.472327855841341]\n",
      "Episode: 1000, [-19.83628724938249, 4.076738705907333, 4.076738705907333]\n",
      "Episode: 1100, [-17.06672783678842, 0.7335456075383633, 0.7335456075383633]\n",
      "Episode: 1200, [-17.87867427946199, 3.0970344289925587, 3.0970344289925587]\n",
      "Episode: 1300, [-17.890356954482915, 2.9307289234705904, 2.9307289234705904]\n",
      "Episode: 1400, [-16.619955993940142, 2.0142552370187348, 2.0142552370187348]\n",
      "Episode: 1500, [-17.39718819104475, 4.014143174200513, 4.014143174200513]\n",
      "Episode: 1600, [-16.40667485842488, 4.749271086875444, 4.749271086875444]\n",
      "Episode: 1700, [-16.950439776372612, 3.407708344293927, 3.407708344293927]\n",
      "Episode: 1800, [-16.626828555679154, 4.289987838854481, 4.289987838854481]\n",
      "Episode: 1900, [-16.432763999059002, 2.152880944338672, 2.152880944338672]\n",
      "Episode: 2000, [-15.082469643058742, 5.251139796567591, 5.251139796567591]\n",
      "Episode: 2100, [-15.122545059156247, 4.2062585020363, 4.2062585020363]\n",
      "Episode: 2200, [-16.45760758782804, 6.63885886083741, 6.63885886083741]\n",
      "Episode: 2300, [-16.56809983002077, 6.5566433509041095, 6.5566433509041095]\n",
      "Episode: 2400, [-15.59516741662699, 5.843432233477919, 5.843432233477919]\n",
      "Episode: 2500, [-15.911809334504246, 6.015376595295181, 6.015376595295181]\n",
      "Episode: 2600, [-15.767663260993466, 5.091372784909311, 5.091372784909311]\n",
      "Episode: 2700, [-15.180903506773815, 4.63242877120398, 4.63242877120398]\n",
      "Episode: 2800, [-16.411906666659153, 4.292312767749502, 4.292312767749502]\n",
      "Episode: 2900, [-16.28750360773459, 6.430434172448703, 6.430434172448703]\n",
      "Episode: 3000, [-16.08698919682327, 5.520181995229281, 5.520181995229281]\n",
      "Episode: 3100, [-16.283247971868544, 5.895211518381204, 5.895211518381204]\n",
      "Episode: 3200, [-15.452306163632198, 5.042311816053743, 5.042311816053743]\n",
      "Episode: 3300, [-17.332084440616413, 5.232721514791147, 5.232721514791147]\n",
      "Episode: 3400, [-15.693863966221704, 4.866513171564397, 4.866513171564397]\n",
      "Episode: 3500, [-15.880445505177102, 5.728106004208765, 5.728106004208765]\n",
      "Episode: 3600, [-14.935212406414102, 4.292447759640539, 4.292447759640539]\n",
      "Episode: 3700, [-15.022355351151685, 4.675989305253514, 4.675989305253514]\n",
      "Episode: 3800, [-16.285319827543866, 5.670355594792792, 5.670355594792792]\n",
      "Episode: 3900, [-15.742759442836933, 5.051877385224075, 5.051877385224075]\n",
      "Episode: 4000, [-15.43149658333669, 4.027508352297984, 4.027508352297984]\n",
      "Episode: 4100, [-15.54598926319783, 6.04797789281444, 6.04797789281444]\n",
      "Episode: 4200, [-15.884948485343825, 4.575628255194767, 4.575628255194767]\n",
      "Episode: 4300, [-16.082157292935808, 4.407947940872514, 4.407947940872514]\n",
      "Episode: 4400, [-15.49215155394325, 5.451533090229851, 5.451533090229851]\n",
      "Episode: 4500, [-15.032682778054976, 4.234758503780162, 4.234758503780162]\n",
      "Episode: 4600, [-16.564549306963332, 6.3840847599872115, 6.3840847599872115]\n",
      "Episode: 4700, [-15.478033542971739, 4.497381447514655, 4.497381447514655]\n",
      "Episode: 4800, [-14.33979657995493, 3.910327236024275, 3.910327236024275]\n",
      "Episode: 4900, [-15.800323110855416, 4.387899341907715, 4.387899341907715]\n",
      "Episode: 5000, [-16.07394960306951, 4.941074322750598, 4.941074322750598]\n"
     ]
    }
   ],
   "source": [
    "for episode in range(NUM_EPISODES):\n",
    "    # Reset the Environment\n",
    "    states_dict, _ = env.reset()\n",
    "    states = [state for state in states_dict.values()]\n",
    "    # Start a New Game\n",
    "    for episode_step in range(LEN_EPISODES):\n",
    "        # Initial States and Actions\n",
    "        actions = maddpg.take_action(states, explore=True)\n",
    "        actions_SN = [np.argmax(onehot) for onehot in actions]\n",
    "        actions_dict = {env.agents[i]: actions_SN[i] for i in range(env.max_num_agents)}\n",
    "        # Step\n",
    "        next_states_dict, rewards_dict, terminations_dict, truncations_dict, _ = env.step(actions_dict)\n",
    "        # Add to buffer\n",
    "        rewards = [reward for reward in rewards_dict.values()]\n",
    "        next_states = [next_state for next_state in next_states_dict.values()]\n",
    "        terminations = [termination for termination in terminations_dict.values()]\n",
    "        buffer.add(states, actions, rewards, next_states, terminations)\n",
    "        # Update States\n",
    "        states = next_states\n",
    "        # Count += 1\n",
    "        total_step += 1\n",
    "        # When the replay buffer reaches a certain size and the number of steps reaches the specified update interval\n",
    "        # 1. Sample from the replay buffer.\n",
    "        # 2. Update Actors and Critics.\n",
    "        # 3. Update Target networks' parameters.\n",
    "        if len(buffer) >= MINIMAL_SIZE and total_step % UPDATE_INTERVAL == 0:\n",
    "            sample = buffer.sample(BATCH_SIZE)\n",
    "            sample = [sample_rearrange(x, device) for x in sample]\n",
    "            # Update Actors and Critics\n",
    "            for agent_idx in range(env.max_num_agents):\n",
    "                maddpg.update(sample, agent_idx)\n",
    "            # Update Target Parameters\n",
    "            maddpg.update_all_targets_params()\n",
    "    # After every 100 rounds, use the evaluate function to evaluate the trained agents, get the reward list ep_returns, and add it to return_list.\n",
    "    if (episode + 1) % 100 == 0:\n",
    "        episodes_returns = evaluate(env.max_num_agents - 1, maddpg, num_episode=100)\n",
    "        return_list.append(episodes_returns)\n",
    "        print(f\"Episode: {episode + 1}, {episodes_returns}\")\n",
    "# Close the Environment\n",
    "env.close()\n",
    "return_array = np.array(return_list)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d2c2e27aeb4cc35",
   "metadata": {
    "id": "d2c2e27aeb4cc35"
   },
   "source": [
    "## 10. Plot"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "846e3f8e",
   "metadata": {},
   "source": [
    "It's time to save and visualize our results."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "e21c1cb0",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 487
    },
    "id": "e21c1cb0",
    "outputId": "014fa2ef-a880-4f8e-e42b-23b90ef6a38b"
   },
   "outputs": [
    {
     "data": {
      "image/png": "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ueeew//+9z/06NEDrVq1wtSpUxESEoLk5GQcOnQI8fHxOH36NADjCbRSqcTcuXORlZUFtVqNfv36wdPTE08//TSee+45jB49GgMHDsTp06exdetWeHh41OhrGzduHF5//XW8/vrrcHNzK3F1/umnn0Z6ejr69esHPz8/XLt2Dd988w3CwsLKNDz85cuX8csvv5RY7uXlhYEDB5oe+/j4YO7cuYiJiUFoaCh+++03hIeHY9GiRaYhz0vbd//+/TFu3Dg0b94cVlZW+P3335GcnGwaEl6j0eCdd97BnDlzMGTIEIwcORIRERH49ttv0bFjR9MgHyqVCh999BGeffZZ9OvXD+PHj0d0dDSWLl1aok/V448/jtWrV+O5557Drl270L17dxgMBly6dAmrV6/G1q1b0aFDB3z44YfYu3cvHnjgAQQGBiIlJQXffvst/Pz80KNHj3u+d7cbMWIE+vbti5kzZyImJgZt2rTBtm3b8Mcff+DVV1+t8DDdTk5O+O677/D444+jXbt2eOSRR6DRaBAbG4tNmzahe/fuWLBgAZycnNCrVy98+umn0Ov18PX1xbZt2xAdHV1inx9//DG2bduG3r17m4adv379OtasWYP9+/dXemLjlStXmoaYL82wYcNgZWWFFStWoHPnznj22WexYMECTJgwAa+88goaNGiAFStWmCZDLq5JUigU+OGHHzB06FC0aNECU6ZMga+vLxISErBr1y44OTlh48aN5Y63ffv2+Oeff/DFF1/Ax8cHwcHB6Ny5c8XfACKqmyw17CARUU0pHt65tCGgDQaDaNiwoWjYsKFpaPGoqCjxxBNPCG9vb6FSqYSvr68YPny4WLt2rdlzFy9eLEJCQoRSqTQbutlgMIi33npLeHh4CDs7OzF48GARGRl5xyHVS4urtGG3hRBi0qRJIjAwsMyvvXv37gKAePrpp0usW7t2rRg0aJDw9PQU1tbWIiAgQDz77LPi+vXr99wv7jKk+q1DlBe/juPHj4uuXbsKGxsbERgYKBYsWGC2v9uHVE9LSxPTpk0TTZs2Ffb29sLZ2Vl07txZrF69ukQsCxYsEE2bNhUqlUp4eXmJ559/XmRkZJTY7ttvvxXBwcFCrVaLDh06iL1794revXubxSuEcQj2uXPnihYtWgi1Wi1cXV1F+/btxZw5c0RWVpYQQogdO3aIBx98UPj4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",
      "text/plain": [
       "<Figure size 1000x500 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "num_agents = return_array.shape[1]\n",
    "df = pd.DataFrame(return_array, columns=[f'agent_{i - 1}' if i > 0 else 'adversary_0' for i in range(num_agents)])\n",
    "df.to_excel('return_data.xlsx', index=False)\n",
    "\n",
    "plt.figure(figsize=(10, 5))\n",
    "plt.xlabel('Episodes')\n",
    "plt.ylabel('Return')\n",
    "plt.title('Return vs Episodes for each Agent')\n",
    "plt.grid(True)\n",
    "\n",
    "for agent in range(num_agents):\n",
    "    agent_returns = return_array[:, agent]\n",
    "    if agent == 0:\n",
    "        plt.plot(range(100, len(agent_returns) * 100 + 100, 100), return_array[:, 0], label=f'adversary_0')\n",
    "    else:\n",
    "        plt.plot(range(100, len(agent_returns) * 100 + 100, 100), agent_returns, label=f'agent_{agent - 1}')\n",
    "\n",
    "plt.legend()\n",
    "plt.savefig('return_plot.png')\n",
    "plt.show()"
   ]
  }
 ],
 "metadata": {
  "accelerator": "GPU",
  "colab": {
   "gpuType": "V100",
   "provenance": []
  },
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.17"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
